/*
 * PipelineDescription.java
 *
 * Created on August 1, 2006, 9:31 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.smallx.xproc;

import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.infoset.xml.Name;

/**
 *
 * @author alex
 */
public class PipelineModel extends ModelItem {

   Set<URI> excludedNamespaces;
   Group mainGroup;
   
   Map<Name,ComponentType> components;
   
   /** Creates a new instance of PipelineDescription */
   public PipelineModel(Name name) 
      throws ModelErrorException
   {
      super(name);
      this.mainGroup = new Group(name,null);
      this.components = new HashMap<Name,ComponentType>();
      this.excludedNamespaces = new HashSet<URI>();
   }
   
   public Set<URI> getExcludedNamespaces() {
      return excludedNamespaces;
   }
   
   public Collection<ComponentType> getComponentTypes() {
      return components.values();
   }
   
   public void importComponents(Collection<ComponentType> types) 
      throws ModelErrorException
   {
      boolean failed = false;
      ModelErrorException error = new ModelErrorException("Cannot import all types as there were conflicts.");
      for (ComponentType type : types) {
         try {
            addComponentType(type);
         } catch (ModelErrorException ex) {
            failed = true;
            error.addError(ex.getMessage());
         }
      }
      if (failed) {
         throw error;
      }
   }
   
   public void addComponentType(ComponentType type) 
      throws ModelErrorException
   {
      if (components.get(type.getName())!=null) {
         throw new ModelErrorException("Component "+type.getName()+" already defined.");
      }
      components.put(type.getName(),type);
   }
  
   public ComponentType getComponentType(Name name) {
      return components.get(name);
   }
   
   /**
    * Resolves all the references and builds the flow graph.
    */
   public void resolve() 
      throws ModelErrorException
   {
      ComponentType pipelineType = mainGroup.getComponentType();
      /*
      ComponentType inputType = new ComponentType(pipelineType.getName());
      for (Name name : pipelineType.getInputPortNames()) {
         PortDefinition def = pipelineType.getInputPort(name);
         inputType.addOutputPort(def.getName(),def.isSequenceAllowed());
      }
      */
      //pipeStep = new Step(pipelineType,null,itemName);
      mainGroup.addStep(mainGroup);
      
      ModelErrorException errors = new ModelErrorException("Invalid pipeline");
      
      // Check existing output bindings
      for (Name name : mainGroup.getStepNames()) {
         Step step = mainGroup.getStep(name);
         for (Name outputName : step.getOutputBindings()) {
            Set<PortReference> bindings = step.getOutputBinding(outputName);
            for (PortReference ref : bindings) {
               PortReference pref = (PortReference)ref;
               Step sourceStep = mainGroup.getStep(pref.getStepName());
               //System.err.println("Checking output "+outputName+" on "+step.getName()+" -> "+pref.getStepName());
               if (sourceStep==null) {
                  errors.addError("Step "+pref.getStepName()+" cannot be found for output port "+outputName+" binding on step "+step.getName());
                  continue;
               }
               if (sourceStep.getComponentType().getOutputPort(pref.getPortName())==null) {
                  errors.addError("Step "+pref.getStepName()+" does not have an output port named "+pref.getPortName());
                  continue;
               }
            }
         }
         if (step instanceof StepContainer) {
            StepContainer container = (StepContainer)step;
            for (Name outputName : step.getComponentType().getOutputPortNames()) {
               PortReference pref = container.getOutputSource(outputName);
               if (pref==null) {
                  errors.addError("There is no output source reference for port "+outputName+" on step container "+step.getName());
                  continue;
               }
               Step containedStep = container.getStep(pref.getStepName());
               if (containedStep==null) {
                  errors.addError("Step "+step.getName()+" does not contain a step called "+pref.getStepName());
                  continue;
               }
               if (containedStep.getContainer()!=container) {
                  errors.addError("Step "+step.getName()+" cannot use output of descendant "+containedStep.getName()+" as it is in a descendant container.");
                  continue;
               }
               if (containedStep.getComponentType().getOutputPort(pref.getPortName())==null) {
                  errors.addError("Step "+pref.getStepName()+" does not have an output port named "+pref.getPortName());
               }
            }
         }
      }
      
      // Check step inputs
      for (Name name : mainGroup.getStepNames()) {
         Step step = mainGroup.getStep(name);
         ComponentType type = step.getComponentType();
         // ensure all inputs are bound
         if (!name.equals(mainGroup.getName())) {
            for (Name inputName : type.getInputPortNames()) {
               if (step.getInputBinding(inputName)==null) {
                  errors.addError("Step "+step.getName()+" is missing a binding for input port "+inputName);
               }
            }
         }
         // Check inputs and reverse bindings
         for (Name inputName : step.getInputBindings()) {
            Reference ref = step.getInputBinding(inputName);
            if (ref instanceof PortReference) {
               PortReference pref = (PortReference)ref;
               Step sourceStep = mainGroup.getStep(pref.getStepName());
               if (sourceStep==null) {
                  errors.addError("Step "+pref.getStepName()+" cannot be found for input port "+inputName+" on step "+step.getName());
                  continue;
               }
               PortDefinition outputPort = sourceStep.getComponentType().getOutputPort(pref.getPortName());
               if (outputPort==null) {
                  if (sourceStep instanceof StepContainer) {
                     StepContainer currentContainer = step instanceof StepContainer ? (StepContainer)step : step.getContainer();
                     while (currentContainer!=null && sourceStep!=currentContainer) {
                        currentContainer = currentContainer.getContainer();
                     }
                     if (currentContainer==null) {
                        errors.addError("Step "+pref.getStepName()+" cannot make reference to port "+pref.getPortName()+" on a step container that is not an ancestor.");
                        continue;
                     }
                  } else {
                     errors.addError("Step "+pref.getStepName()+" does not have an output port named "+pref.getPortName());
                     continue;
                  }
               }
               try {
                  sourceStep.bindOutput(pref.getPortName(),new PortReference(step.getName(),inputName));
               } catch (ModelErrorException ex) {
                  errors.addError(ex.getUnformattedMessage());
               }
            }
         }
      }
      if (errors.getErrors().hasNext()) {
         throw errors;
      }
   }
   
   public Group getPipelineGroup() {
      return mainGroup;
   }

}
